package com.lightsh.exception;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.text.format.DateFormat;
import android.util.Log;

/**
 * Android应用程序的异常捕获类，用于捕获导致应用程序崩溃的异常。
 * <br>可将异常写入文件或者通过网络发送到指定服务器
 * 
 *
 *
 *  <p>使用方法:
 *  <br>1.android应用程序需要重写自己的Application类,重写onCreate()方法，下面是一个示例：
 * 				<br>@Override
				<br>public void onCreate() {
					<br>super.onCreate();
					<br>CrashExceptionHandler crashHandler = CrashExceptionHandler.getInstance();
					<br>crashHandler.init(getApplicationContext());
				<br>}
	<br>默认的log文件保存路径为/sdcard/CrashLog/，保存的文件名为CrashExceptionLog.txt。
	<br>如果有需要，可在{@link CrashExceptionHandler#init(Context, String, String)}方法中指定log文件保存的路径。
	
	<p>2.在AndroidManifest文件的"application"标签中声明自己的Application类: android:name=".MyApplication"
 */

public class CrashExceptionHandler implements UncaughtExceptionHandler {
	
	private String TAG = "CrashHandler";
	public static final boolean DEBUG = true;
	private String mLogDir = "/sdcard/LCrashLog/";
	private String mLogFileName = "CrashExceptionLog.txt";
	private String mLogServerUrl = null;
	
	/**
	 * 在没有sdcard或者需要保存的log文件无法创建时，将数据保存在用户的preference中。
	 * 下次进入时，如果目录可创建，则将上一次没有保存的数据保存，并删除对应的preference数据
	 */
	private boolean mbSavePreference = false;
	private static final String CRASH_EXCEPTION_PREFERENCE = "CrashExceptionPreference";
	private static final String KEY_CRASH_EXCEPTION = "KeyCrashException";
	
	/** 系统默认的UncaughtException处理类 */  
    private Thread.UncaughtExceptionHandler mDefaultHandler;  
    /** CrashHandler实例 */  
    private static CrashExceptionHandler INSTANCE;  
    /** 程序的Context对象 */  
	private Context mContext;
	
	
    /** 保证只有一个CrashHandler实例 */  
    private CrashExceptionHandler() {}  
    /** 获取CrashHandler实例 ,单例模式*/  
	public static CrashExceptionHandler getInstance() {
		if (INSTANCE == null) {
			INSTANCE = new CrashExceptionHandler();
		}
		return INSTANCE;
	}
	
	/** 
     * 初始化,注册Context对象, 
     * 获取系统默认的UncaughtException处理器, 
     * 设置该CrashHandler为程序的默认处理器 
     *  
     * @param ctx 
     */  
    public void init(Context ctx) {
        mContext = ctx;  
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
        checkHistoryException();
    }  
    
    /** 
     * 初始化,注册Context对象, 
     * 获取系统默认的UncaughtException处理器, 
     * 设置该CrashHandler为程序的默认处理器 
     *  
     * @param ctx 
     * @param logPath 错误日志保存路径
     * @param serverUrl 如需要发送到服务器，传入url，否则null
     */  
    public void init(Context ctx, String logPath, String serverUrl){
    	mLogDir = logPath;
    	if(mLogDir != null && !mLogDir.endsWith("/")){
    		mLogDir += "/";
    	}
    	mLogServerUrl = serverUrl;
    	this.init(ctx);
    }
  
    /** 
     * 当UncaughtException发生时会转入该函数来处理 
     */  
	public void uncaughtException(Thread thread, Throwable ex) {
		ex.printStackTrace();
		// 自己的异常处理方法
		if (!handleException(ex)) {  
            // 没有处理成功
			if(DEBUG){
				Log.e(TAG, "handleException not success");
			}			
        }
		
		// 始终调用系统默认的退出方式
		if(mDefaultHandler != null){
			mDefaultHandler.uncaughtException(thread, ex);
		} else {
			//Sleep一会后结束程序  
            try {  
                Thread.sleep(3000);  
            } catch (InterruptedException e) {  
                Log.e(TAG, "Error : ", e);  
            }  
            android.os.Process.killProcess(android.os.Process.myPid());  
            System.exit(10);  
		}
	}
	
	/** 
     * 自定义错误处理,收集错误信息 
     * 发送错误报告等操作均在此完成. 
     * @param ex 
     * @return true:如果处理了该异常信息;否则返回false 
     */  
	private boolean handleException(Throwable ex) {
		if (ex == null) {
			return true;
		}
		if(DEBUG){
			// 使用Toast来显示异常信息
			//final String msg = ex.getLocalizedMessage();
			//Toast.makeText(mContext, "程序出错啦:" + msg, Toast.LENGTH_LONG).show();
		}

		// 保存错误报告文件
		saveCrashInfoToFile(ex);
		
		// 发送错误报告到服务器
		sendCrashReportsToServer(ex);
		return true;
	}

	/**
	 * 把错误报告发送给服务器
	 * 
	 */
	private void sendCrashReportsToServer(Throwable ex) {
		if(mLogServerUrl != null){
			// 进行数据发送(需要考虑延迟程序退出时间)
		}
	}

	/**
	 * 保存错误信息到文件中
	 * 
	 * @param ex
	 * @return
	 */
	private String saveCrashInfoToFile(Throwable ex) {
		Writer info = new StringWriter();
		PrintWriter printWriter = new PrintWriter(info);
		ex.printStackTrace(printWriter);

		Throwable cause = ex.getCause();
		while (cause != null) {
			cause.printStackTrace(printWriter);
			cause = cause.getCause();
		}

		String logString = info.toString();
		printWriter.close();
		
		int verCode = -1;
		PackageManager pm = mContext.getPackageManager();
		PackageInfo pi;
		try {
			pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
			if (pi != null) {
				verCode = pi.versionCode;
			}
		} catch (NameNotFoundException e1) {
			e1.printStackTrace();
		}

		try {
			long timeStamp = System.currentTimeMillis();
			File pathFile = new File(mLogDir);
			if(!pathFile.exists() && !pathFile.mkdirs()){
					// 创建目录失败,保存
				saveToPreference(verCode, timeStamp, logString);
				return null;
			} else {
				if(!pathFile.isDirectory()){
					// 不是目录
					pathFile.delete();
					pathFile.mkdirs();
				}
				File logFile = new File(mLogDir + mLogFileName);
				if(logFile.exists() || logFile.createNewFile()){
					// 在文件后面写入
					appendLog(logFile, verCode, timeStamp, logString);
				}else{
					// 文件不存并且创建出错
					saveToPreference(verCode, timeStamp, logString);
				}
			}
			return mLogDir + mLogFileName;
		} catch (Exception e) {
			Log.e(TAG, "an error occured while writing report file...", e);
		}
		return null;
	}
	
	/**
	 * 保存数据到preference
	 */
	private void saveToPreference(int verCode, long timeStamp, String logString){
		if(mbSavePreference){
			SharedPreferences preference = mContext.getSharedPreferences(CRASH_EXCEPTION_PREFERENCE, 0);
			if(preference == null){
				return;
			}
			String strResult = getLogString(verCode, timeStamp, logString);
			SharedPreferences.Editor editor = preference.edit();
			editor.putString(KEY_CRASH_EXCEPTION, preference.getString(KEY_CRASH_EXCEPTION, "") + strResult);
			editor.commit();
			
		}
	}
	
	/**
	 * 在程序開始時，如果目录存在并且存在未写入到文件的数据，则写入并删除
	 */
	
	private void checkHistoryException(){
		try{
			SharedPreferences preference = mContext.getSharedPreferences(CRASH_EXCEPTION_PREFERENCE, 0);
			if(preference == null){
				return;
			}
			String strResult = preference.getString(KEY_CRASH_EXCEPTION, null);
			if(strResult != null && strResult.length() > 0){
				File pathFile = new File(mLogDir);
				if(!pathFile.exists() && !pathFile.mkdirs()){
						// 创建目录失败,保存
					return;
				} else {
					if(!pathFile.isDirectory()){
						// 不是目录
						pathFile.delete();
						pathFile.mkdirs();
					}
					File logFile = new File(mLogDir + mLogFileName);
					if(logFile.exists() || logFile.createNewFile()){
						// 在文件后面写入							
						appendLog(logFile, strResult);
						SharedPreferences.Editor editor = preference.edit();
						editor.remove(KEY_CRASH_EXCEPTION);
						editor.commit();
					}else{
						// 文件不存并且创建出错
						return;
					}
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	/**
	 * 追加log内容到文件尾部
	 */
	private void appendLog(File logFile, int verCode, long timeStamp, String logString){
		try {
			FileOutputStream out = new FileOutputStream(logFile, true);
			OutputStreamWriter writer = new OutputStreamWriter(out);
			writer.write(getLogString(verCode, timeStamp, logString));
			writer.flush();
			out.flush();
			writer.close();
			out.close();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private String getLogString(int verCode, long timeStamp, String logString){
		StringBuilder builder = new StringBuilder();
		builder.append("----------------------------------------\n");
		builder.append("Time: " + DateFormat.format("yyyy-MM-dd kk:mm", timeStamp) + "\n");
		builder.append("verCode: " + verCode  + "\n");
		builder.append(logString);
		builder.append("\n\n");
		return builder.toString();		
	}
	
	private void appendLog(File logFile, String log){
		FileOutputStream out;
		try {
			out = new FileOutputStream(logFile, true);
			OutputStreamWriter writer = new OutputStreamWriter(out);
			writer.write(log);
			writer.flush();
			out.flush();
			writer.close();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	

}
